
-- websocket 工具
-- auth :diandian
-- date :2017/3/18
--

local socket = require "socket"
local ddlog = require "ddlog"
local crypt        = require 'crypt'
local sha1         = crypt.sha1
local base64encode = crypt.base64encode

local M = {}

local HEAD_LIMIT = 8192
local guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

local function sec_accept (key)
  return base64encode(sha1(key..guid))
end

function M.read_http_head(fd)
    local head_str = ""
    local msg = socket.read(fd)
    while msg do
        head_str = head_str .. msg
        if #head_str > HEAD_LIMIT then
            return nil
        end
        local b, e = head_str:find("\r\n\r\n", -#head_str - 3, true)
        if b then
            head_str:sub(1, e)
            break
        end
        msg = socket.read(fd)
    end

    if head_str:find "^\r\n" then return nil end

    local s = head_str:find "\r\n"
    if not s then  return nil end
    local first_line = head_str:sub(0, s)
    head_str = head_str:sub(s+2, -1)
    local head = {}
    for line in string.gmatch(head_str, "([^\r\n]+)") do
        local s = line:find(":")
        if s then
            local k = line:sub(0, s - 1)
            local v = line:sub(s+2, -1)
            head[k] = v
        else
            return nil
        end
    end
    return first_line, head
end

function M.check_head(head)
    if not head["Upgrade"] or head["Upgrade"] ~= "websocket" then
        print("not Upgrade")
    end

    if not head["Sec-WebSocket-Key"] then
        print("not Sec-WebSocket-Key")
        return false
    end

    if not head["Connection"] or head["Connection"] ~= "Upgrade" then
        print("not Sec-WebSocket-Key")
        return false
    end

    return true
end

function M.make_respone(first_line, head)
    local line  = {
        "HTTP/1.1 101 Switching Protocols",
        "Upgrade: websocket",
        "Connection: "..head["Connection"],
        --"Sec-WebSocket-Protocol: chat",
        string.format("Sec-WebSocket-Accept: %s",sec_accept(head["Sec-WebSocket-Key"])),
        "\r\n",
    }
    return table.concat(line, "\r\n")
end

function M.make_respone_debug(code, detail)
    return string.format("HTTP/1.1 %d %s\r\n\r\n", code, detail)
end

function M.make_read_pack(fd)
    local B1, B2
    local OPCODE

    return function()
        if not B1 then
            B1 = string.unpack(">I1", data)
            if  B1 & 0xF == 0x8 then -- OPCODE
                return
            end

            local B2 = string.unpack(">i1", data, 2)
            local PAYLOAD1 = B2 & 0x7F
            local data_size = 0
            if PAYLOAD1 <= 125 then
                data_size = PAYLOAD1
            elseif PAYLOAD1 == 126  then
                data = socket.read(fd, 2)
                data_size = string.unpack(">I2", data)
            elseif(PAYLOAD1) == 127 then
                data = socket.read(fd, 8)
                data_size = string.unpack(">I8", data)
            else
                return
            end
        end
    end
end

function M.read_pack(fd)
    local data = socket.read(fd, 2)
    if not data then 
        return nil, ddlog.debug("not data")
    end

    local B1 = string.unpack(">I1", data)
    local OPCODE = B1 & 0xF

    if OPCODE == 0x8 then
        return nil, ddlog.debug("OPCODE == 0x8")
    end

    local B2 = string.unpack(">i1", data, 2)
    local MASK = B2 & 0x80 >> 7
    local PAYLOAD1 = B2 & 0x7F

    local data_size = 0
    if PAYLOAD1 <= 125 then
        data_size = PAYLOAD1
    elseif PAYLOAD1 == 126  then
        data = socket.read(fd, 2)
        data_size = string.unpack(">I2", data)
    elseif(PAYLOAD1) == 127 then
        data = socket.read(fd, 8)
        data_size = string.unpack(">I8", data)
    else
        return nil, ddlog.debug("PAYLOAD1", PAYLOAD1)
    end

    if not data then
        return nil, ddlog.debug("not data")
     end

    local masks
    if MASK then
        data = socket.read(fd, 4)
        if not data then return end
        masks = {
            string.unpack(">I1", data),
            string.unpack(">I1", data, 2),
            string.unpack(">I1", data, 3),
            string.unpack(">I1", data, 4),
        }
    end
    data = socket.read(fd, data_size)
    if not data then return end
    if MASK then
        local mask_data = {}
        for i=1, data_size do
            local byte = string.byte(data, i)
            byte = (byte ~ masks[(i - 1) % 4 + 1]) & 0xffffffff
            table.insert(mask_data, string.char(byte))
        end
        data = table.concat(mask_data)
    end
    --[[
    if (B1 & 0x80) == 0 then
        local next_pack = M.read_pack(fd)
        if not next_pack then return end
        data = data .. next_pack
    end
    ]]
    return data
end

function M.make_send(data, size)
    local b1 = 0x82
    local b2
    if size <= 125 then
        b2 = size
    else
        b2 = 126
    end
    local str = string.pack(">I1>I1", b1, b2)
    if b2 == 126 then
        str = str .. string.pack(">I2", size)
    end
    str = str .. data
    return str
end

return M
